<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>

</body>
<script>
    // 【1】typeof【这也是个一元预算符，这意味着，后面不用非要小括号】
    // 我们最最常用的莫过于 typeof，注意，尽管我们会看到诸如：
    //       console.log(typeof('yayu')) // string
    // 的写法，但是 typeof 可是一个正宗的运算符，就跟加减乘除一样！这就能解释为什么下面这种写法也是可行的：
    //       console.log(typeof 'yayu') // string


    // 数据类型六种：Undefined、Null、Boolean、Number、String、Object
    // 然而当我们使用 typeof 对这些数据类型的值进行操作的时候，返回的结果却不是一一对应，分别是：
    // undefined、object、boolean、number、string、object【以上都是小写的字符串，但是返回6种类型，还有一个function不在此列】

    // 尽管不能一一对应，但是 typeof 却能检测出函数类型：

    function a() {}

    console.log(typeof a); // function
    // 除此之外 Object 下还有很多细分的类型呐， 如 Array、 Function、 Date、 RegExp、 Error 等。
    // 如果用 typeof 去检测这些类型， 举个例子：

    var date = new Date();
    var error = new Error();
    console.log(typeof date); // object
    console.log(typeof error); // object
    // 返回的都是 object 呐， 这可怎么区分~所以有没有更好的方法呢？

    // 【2】Object.prototype.toString
    // ES5 规范解释：
    //当 toString 方法被调用的时候，下面的步骤会被执行：
    // 1，如果 this 值是 undefined，就返回 [object Undefined]
    // 2，如果 this 的值是 null，就返回 [object Null]
    // 3，让 O 成为 ToObject(this) 的结果
    // 4，让 class 成为 O 的内部属性 [[Class]] 的值
    // 5，最后返回由 "[object " 和 class 和 "]" 三个部分组成的字符串

    // 实用性的识别，为11种：
    // 以下是11种：
    var number = 1; // [object Number]
    var string = '123'; // [object String]
    var boolean = true; // [object Boolean]
    var und = undefined; // [object Undefined]
    var nul = null; // [object Null]
    var obj = {
        a: 1
    }; // [object Object]
    var array = [1, 2, 3]; // [object Array]
    var date = new Date(); // [object Date]
    var error = new Error(); // [object Error]
    var reg = /a/g; // [object RegExp]
    var func = function a() {}; // [object Function]

    function checkType() {
        for (var i = 0; i < arguments.length; i++) {
            console.log(Object.prototype.toString.call(arguments[i]))
        }
    }

    checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func);
    // 还有两种【实战中下面两种并不常用】
    console.log(Object.prototype.toString.call(Math)); // [object Math]
    console.log(Object.prototype.toString.call(JSON)); // [object JSON]
    // 除了以上 13 种之外，还有 ：

    function a() {
        console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
    }
    a();
    // 所以我们可以识别至少 14 种类型，当然我们也可以算出来，[[class]] 属性至少有 12 个。

    //【3】type API
    // 既然有了 Object.prototype.toString 这个神器！那就让我们写个 type 函数帮助我们以后识别各种类型的值吧！ 
    //【3-1】第一版
    var class2type = {};

    // 生成class2type映射
    "Boolean Number String Function Array Date RegExp Object Error Null Undefined".split(" ").map(function(item, index) {
        // class2type为对象， 每一个所有为数组， 每个元素被映射为[object Date] 后面那个Date的小写
        class2type["[object " + item + "]"] = item.toLowerCase();
    })

    function type(obj) {
        // 下面代码的巧妙之处，在于直接将toString的返回参作为一个对象索引读取了映射的value
        return typeof obj === "object" || typeof obj === "function" ?
            class2type[Object.prototype.toString.call(obj)] || "object" :
            typeof obj;
    }
    type(new Date());

    // 但是注意，在 IE6 中，null 和 undefined 会被 Object.prototype.toString 识别成 [object Object]！
    //【3-2】第二版
    var class2type = {};

    // 生成class2type映射
    "Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
        class2type["[object " + item + "]"] = item.toLowerCase();
    })

    function type(obj) {
        // 一箭双雕【此处如果是undefined或null，下面是return出undefined或null，在type函数底部被typeof重新抓取。】
        //此处这么写的目的，是结合了type函数底部的return语句去规避ie的Object.prototype.toString函数的bug
        if (obj == null) {
            debugger
            return obj + "";
        }
        return typeof obj === "object" || typeof obj === "function" ?
            class2type[Object.prototype.toString.call(obj)] || "object" :
            typeof obj;
    }

    //【3-3】isFunction
    // 此处，基于上述函数做了二次封装
    // 有了 type 函数后， 我们可以对常用的判断直接封装， 比如 isFunction:

    function isFunction(obj) {
        return type(obj) === "function";
    }
    //【3-3】数组
    // jQuery 判断数组类型，旧版本是通过判断 Array.isArray 方法是否存在，如果存在就使用该方法，不存在就使用 type 函数。
    // 但是在 jQuery v3.0 中已经完全采用了 Array.isArray。
    var isArray = Array.isArray || function(obj) {
        return type(obj) === "array";
    }
</script>

</html>